goog.provide('dkpoints.status');

goog.require('soy');
goog.require('goog.dom');
goog.require('goog.math');
goog.require('goog.style');
goog.require('goog.dom.ViewportSizeMonitor');

goog.require('dkpoints.utils');
goog.require('dkpoints.status.templates');

// Constantly used constant.  Make really cheap to get.
dkpoints.status.WIDTH = 175;

// Track the status pane's state.
dkpoints.status.displayed = false;

// The list of status messages.  Initialized in init().
dkpoints.status.list = null;

// Get the starting point for the animated fly-in.
dkpoints.status.getAnimShowStart = function() {
  // Start the status bar 1 pixel off the right side.
  return (dkpoints.status.vsm.getSize().width + 1);
};

// Calculate the size and position of the status bar.
dkpoints.status.calcSizeAndPos = function() {
  var me = dkpoints.status;

  // Get window size.
  var size = me.vsm.getSize();
  
  var sizeAndPos = {};
  
  // Top and Height
  sizeAndPos.top = goog.style.getPosition(goog.dom.$('body_content')).y + 25
  sizeAndPos.height = size.height - sizeAndPos.top - 50;
  
  // Width and Left
  sizeAndPos.width = me.WIDTH;
  sizeAndPos.left = size.width - sizeAndPos.width - 5;
  
  return sizeAndPos;
};

/**
 * Resize the Status Pane.  Also called on loadUI();
 * @param {Object=} event The event object passed in when called by a resize event handler.
 */
dkpoints.status.resize = function(event) {
  var me = dkpoints.status;
  
  // This method can be called when the VSM is initialized.
  // Don't do anything if the status window isn't ready yet.
  if(me.displayed || me.initializing) {
    var me = dkpoints.status;
    var pane = goog.dom.$('statusPane');
    
    // Get object w/ dimensions and positions.
    var dims = me.calcSizeAndPos();
    
    goog.style.setSize(pane, new goog.math.Size(dims.width, dims.height));
    goog.style.setPosition(pane, dims.left, dims.top);
  }
};

dkpoints.status.animHide = function() {
  var me = dkpoints.status;
  
  if(me.displayed) {   
    var pane = me.statusDiv;
    var screenWidth = me.vsm.getSize().width;
    var paneLeft = parseInt(pane.style.left, 10);
    
    var shifted = 0;
    if(paneLeft < screenWidth) {
      pane.style.left = (paneLeft + 5) + "px";
      setTimeout(me.animHide, 1);
    }
    else {
      me.displayed = false;
    }
    
    if(me.list && me.list.count > 0 && (! me.showStatusButton.parentNode)) {
      var body = goog.dom.$('body_content');
      body.insertBefore(me.showStatusButton, body.firstChild);
    }
    
    me.updateShowButton();
  }
};

dkpoints.status.animShow = function() {
  var me = dkpoints.status;
  var pane = me.statusDiv;
  
  if(! me.displayed) {
    if(me.showStatusButton.parentNode) {
      me.showStatusButton.parentNode.removeChild(me.showStatusButton);
    }
    
    var currLeft = parseInt(pane.style.left, 10);
    var stopLeft = me.vsm.getSize().width - me.WIDTH;
    
    if(currLeft > stopLeft) {
      var shift = Math.min(5, currLeft - stopLeft);
      pane.style.left = (currLeft - shift) + "px";
      setTimeout(me.animShow, 1);
    }
    else {
      me.displayed = true;
    }
  }  
};

dkpoints.status.init = function() {
  // Get the CSS downloaded.
  dkpoints.utils.loadCSS('statuspane');
  var me = dkpoints.status;
  
  if(! me.statusDiv) {
    // Set up the Status Pane div and store a reference to avoid later lookups.
    var statusDiv = goog.dom.createDom('div');
    me.statusDiv = statusDiv;
    
    statusDiv.setAttribute('id', 'statusPane');
    statusDiv.style.display = "none";
    
    // Set up the status window.
    soy.renderElement(statusDiv, dkpoints.status.templates.loadBase, {})
    
    // Attach click handlers to the hide and clear buttons.
    document.body.appendChild(statusDiv);
    var hideStatus = goog.dom.$('statusCloseButton');
    var clearButton = goog.dom.$('statusClearButton');
    
    goog.events.listen(hideStatus, goog.events.EventType.CLICK, me.animHide, false, me);
    goog.events.listen(clearButton, goog.events.EventType.CLICK, me.clearAll, false, me);
    
    //  Set up the ViewportSizeMonitor
    if((! me.vsm) || me.vsm == null) {
      me.vsm = new goog.dom.ViewportSizeMonitor();
      goog.events.listen(me.vsm, goog.events.EventType.RESIZE, me.resize, false, me);
    }
    
    me.showStatusButton = goog.dom.createDom('button');
    me.showStatusButton.setAttribute('id', 'showStatusButton');
    me.showStatusButton.innerHTML = "<< Show Status";
    
    goog.events.listen(me.showStatusButton, goog.events.EventType.CLICK, me.animShow, false, me);
    
    // Put the status bar to the right side of the screen.
    me.initializing = true;
    me.resize();
    delete me.initializing;
    
    statusDiv.style.left = (me.vsm.getSize().width + 1) + "px";
    
    // Show it.
    statusDiv.style.display = "block";
    me.animShow();
  }
  
  if(! me.displayed) {
    me.animShow();
  }
  
  me.list = {};
  me.list.count = 0;
};

dkpoints.status.teardown = function() {
  var me = dkpoints.status;
  
  // Tear down event listeners and dereference ViewportSizeMonitor to allow garbage collection.
  goog.events.unlisten(me.vsm, goog.events.EventType.RESIZE, me.resize, false, me);
  me.vsm = null;
  
  // Tear down the event listener for the status close button.
  var hideStatus = goog.dom.$('statusCloseButton');
  goog.events.unlisten(hideStatus, goog.events.EventType.CLICK, me.animHide, false, me);
  
  // Tear down the event listener for the clear status button.
  var clearStatus = goog.dom.$('statusClearButton');
  goog.events.unlisten(clearStatus, goog.events.EventType.CLICK, me.clearAll, false, me);
  
  // Tear down the event listener for the Show Status button.
  goog.events.unlisten(me.showStatusButton, goog.events.EventType.CLICK, me.animShow, false, me);
  if(me.showStatusButton.parentNode) {
    me.showStatusButton.parentNode.removeChild(me.showStatusButton);
  }
  me.showStatusButton = null;
  
  // Remove all messages and delete references to allow garbage collection
  me.statusDiv.innerHTML = "";
  me.list = null;
  
  // Pull status pane off the page and dereference.
  me.statusDiv.parentNode.removeChild(me.statusDiv);
  me.statusDiv = null;
  
  me.displayed = false;
};

/*
 * Public functions
 *
 */

/**
 * Add a message to the status pane
 * @param {string} message The message to be added.
 * @param {string} messageID ID to use to access the message later.
 * @param {string=} groupName Name of the group of messages to add this to.  Creates group if it doesn't yet exist.
 * @param {number=} remDelay Number of milliseconds to wait before automatically removing.
 * @return {boolean} Whether or not the message was successfully added.
 */
dkpoints.status.addMessage = function(message, messageID, groupName, remDelay) {
  var me = dkpoints.status;

  // Make sure the status pane exists and is visible.
  if(! me.statusDiv) {
    me.init();
  }
  
  if(!me.displayed) {
    me.animShow();
  }
  
  // A message and a message ID are both required.
  if((! message) || (! messageID)) {
    throw Error("Missing status message params.");
  }
  
  // Prepend prefix to avoid collisions between standalones and groups.
  messageID = "m_" + messageID;
  
  // Make sure it exists first.
  var groupTitle = null;
  if(groupName != null) {
    groupTitle = groupName;
    groupName = "g_" + groupName;
  }
  
  else if(! me.displayed) {
    me.animShow();
  }
  
  var list = me.list;
  var messageEl = null;
  
  if(groupName) {
    // Caller provided a group for the message
    
    var group = null;
    if(list[groupName]) { // Group exists.
      group = list[groupName];
      
      if(group[messageID]) {
        // Message already exists.  Don't overwrite.
        return false;
      }
      else {
        // Build message div.
        messageEl = goog.dom.createDom('div');
        soy.renderElement(messageEl, me.templates.statusMessage, {message: message});
        
        // Add reference to message div to the group object and render on screen.
        group[messageID] = messageEl;
        group.base.appendChild(messageEl);
        ++list.count;
      }
    }
    else {
      // New group.
      group = list[groupName] = {};
      
      // Build group data object required by template.
      var groupData = {
        groupname: groupTitle,
        message: { message: message }
      };
      
      // Generate group div, save references, and render.
      var groupEl = goog.dom.htmlToDocumentFragment(me.templates.newGroup(groupData, null));
      group.base = groupEl;
      group[messageID] = groupEl.childNodes[1];
      
      goog.dom.$('statusContent').appendChild(groupEl);
      ++list.count;
    }
    // end: if(list[groupName]) { // Group exists.
  }
  else {
    // Group name was not supplied.  Add a stand-alone message.
    
    if(list[messageID]) {
      // Message exists.  Don't overwrite.
      return false;
    }
    else {
      // New message.  Generate div.
      
      messageEl = goog.dom.htmlToDocumentFragment(me.templates.statusMessage( { message: message }, null ));
      list[messageID] = messageEl;
      
      goog.dom.$('statusContent').appendChild(messageEl);
      ++list.count;
    }
  }
  
  if(remDelay) {
    // RemDelay specifies the amount of time to wait before removing
    // the added message.  Set up a timeout to handle the removal.
    if(groupName) {
      setTimeout(me.removeOnDelay(messageID, groupName), remDelay);
    }
    else {
      setTimeout(me.removeOnDelay(messageID), remDelay);
    }
  }
  
  return true;
};

/**
 * Update a message already in the status pane
 * @param {string} message Updated text for the message.
 * @param {string} messageID ID of message to be updated
 * @param {string=} groupName Group to which the message belongs.
 * @param {number=} remDelay Number of milliseconds to wait before automatically removing.
 * @return {boolean} Whether or not the message was successfully added.
 */
dkpoints.status.updateMessage = function(message, messageID, groupName, remDelay) {
  var me = dkpoints.status;
  var list = me.list;
  
  if(!me.displayed) {
    me.animShow();
  }
  
  // A message and a message ID are both required.
  if((! message) || (! messageID)) {
    throw Error("Missing status message params.");
  }
  
  // Prepend prefix to avoid collisions between standalones and groups.
  messageID = "m_" + messageID;
  
  // Make sure it exists first.
  if(groupName != null) {
    groupName = "g_" + groupName;
  }
  
  var updated = false;
  var messageEl = null;
  if(groupName) {
    // User passed in a group.  Make sure it exists.
    
    // Check that the group exists
    if(list[groupName]) {
      var group = list[groupName];
      
      // Check that message exists in this group.
      if(group[messageID]) {
        messageEl = group[messageID];
        messageEl.innerHTML = soy.$$escapeHtml(message);
        
        updated = true;
      } // else: 'updated' will return as false;
    } // else: 'updated' will return as false;
  }
  else {
    // User is updating a standalone message.
    
    // Check that message exists in the list.
    if(list[messageID]) {
      messageEl = list[messageID];
      messageEl.innerHTML = soy.$$escapeHtml(message);
        
      updated = true;
    } // else: 'updated' will return as false;
  }
  
  if(updated && remDelay) {
    // Message was updated, now set timer to remove after X ms.
    
    if(groupName) {
      setTimeout(me.removeOnDelay(messageID, groupName), remDelay);
    }
    else {
    setTimeout(me.removeOnDelay(messageID), remDelay);
    }
  }

  return updated;
};

/**
 * Remove a message from the status pane
 * @param {string} messageID ID of message to be removed.
 * @param {string=} groupName The name of the group to which the message belongs.
 * @param {number=} remDelay Remove message after remDelay (ms).
 * @return {boolean} Whether or not the message was successfully removed.
 */
dkpoints.status.removeMessage = function(messageID, groupName, remDelay) {
  if(remDelay && remDelay > 0) {
    setTimeout(function() { dkpoints.status.removeMessage(messageID, groupName) }, remDelay);
  }
  else {
    var me = dkpoints.status;
    var list = me.list;
    
    // Prepend prefix to avoid collisions between standalones and groups.
    // When passed in by removeOnDelay, they'll already have 'm_' and 'g_'
    if(messageID.substring(0, 2) != "m_") {
      messageID = "m_" + messageID;
    }
    if(groupName && groupName.substring(0, 2) != "g_") {
      groupName = "g_" + groupName;
    }
    
    var messageEl = null;
    if(groupName) {
      // User passed in a group.  Get the group object.
      if(list[groupName]) {
        var group = list[groupName];
        
        // Get the messageEl from the group.
        if(group[messageID]) {
          messageEl = group[messageID];
          
          // Remove message from DOM and group object.
          messageEl.parentNode.removeChild(messageEl);
          delete group[messageID];
          --list.count;
          
          var groupEl = group.base;
          
          if(groupEl.childNodes.length == 1) {
            // Group is now empty except for the header div.  Remove.
            groupEl.parentNode.removeChild(groupEl);
            delete list[groupName];
          }
          
          // Check if status pane is displayed
          if(me.displayed) {
            // If there are no messages left, hide it.
            if(! list.count) {
              me.animHide();
            }
          }
          else { // Hidden
            // Any messages left?
            if(list.count) {
              // Yes.  Update the button.
              me.updateShowButton();
            }
            else {
              // No.  Remove the button.
              var showButton = me.showStatusButton;
              if(showButton.parentNode) {
                showButton.parentNode.removeChild(showButton);
              }
            }
          }
          
          return true;
        }
      }
    }
    else {
      // User didn't enter a group.  Check if the message lives on the list.
      if(list[messageID]) {
        messageEl = list[messageID];
        
        // Remove from DOM and list.
        messageEl.parentNode.removeChild(messageEl);
        delete list[messageID];
        
        --list.count;
        // Check if status pane is displayed
        if(me.displayed) {
          // If there are no messages left, hide it.
          if(! list.count) {
            me.animHide();
          }
        }
        else { // Hidden
          // Any messages left?
          if(list.count) {
            // Yes.  Update the button.
            me.updateShowButton();
          }
          else {
            // No.  Remove the button.
            var showButton = me.showStatusButton;
            if(showButton.parentNode) {
              showButton.parentNode.removeChild(showButton);
            }
          }
        }
        
        return true;
      }
    }
  }
  
  // None of the above control flows lead to an element being removed.
  return false;
};

/**
 * Produces the closure used to remove the element as part of an auto-remove timeout.
 * @param {string} messageID ID of the message to be removed
 * @param {string=} group The name of the group to which the message belongs.
 * @return {function()} Closure which calls removeMessage for setTimeout
 */
dkpoints.status.removeOnDelay = function(messageID, group) {
  if(group) {
    return (function() {
      dkpoints.status.removeMessage(messageID, group);
    });
  }
  else {
    return (function() {
      dkpoints.status.removeMessage(messageID);
    });
  }
}

/**
 * Clear all messages
 * @return {number} The number of messages removed.
 */
dkpoints.status.clearAll = function() {
  var me = dkpoints.status;
  
  var content = goog.dom.$('statusContent');
  var messages = me.list.count;
  
  content.innerHTML = "";
  
  me.list = {};
  me.list.count = 0;
  
  me.animHide();
  return messages;
};

/**
 * Updates the counter on the Show Status button.
 */
dkpoints.status.updateShowButton = function() {
  var me = dkpoints.status;
  var count = me.list.count;
  
  var end = " messages)";
  if(count == 1) {
    end = " message)";
  }
  
  me.showStatusButton.innerHTML = "&lt;&lt; Show Status (" + count + end;
   
};